﻿using RockBus.DataContracts;
using RockBus.Hosting.Interfaces;
using RockBus.Logging.Interfaces;
using RockBus.ServiceModel;
using RockBus.Transport.Interfaces;
using RockBus.Transport.Wcf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;

namespace RockBus.Hosting
{
    public class ReceivePortServiceHostBase<TService> : RockBusServiceHost, IReceivePortServiceHost
    {
        public ReceivePortServiceHostBase(ILogger logger, EndpointInfo endpointInfo, PublisherReceivePortInfo publisherReceivePortInfo = null)
            : base(logger)
        {
            this.EndpointInfo = endpointInfo;
            this.PublisherReceivePortInfo = publisherReceivePortInfo;

            this.ServiceEndpointFactory = new ServiceEndpointFactory();
            this.TemplateServiceElement = ConfigurationHelper.GetServiceElement<TService>();
        }

        public EndpointInfo EndpointInfo { get; private set; }

        public PublisherReceivePortInfo PublisherReceivePortInfo { get; private set; }

        public Uri ListenUri { get; private set; }

        protected IServiceEndpointFactory ServiceEndpointFactory { get; set; }

        protected ServiceElement TemplateServiceElement { get; set; }

        protected Uri[] ReceivePortBaseAddresses { get; set; }

        protected override ServiceDescription CreateDescription(out IDictionary<string, ContractDescription> implementedContracts)
        {
            var description = base.CreateDescription(out implementedContracts);

            implementedContracts.Clear();
            var contract = this.ServiceEndpointFactory.GetContractDescription(this.EndpointInfo);
            implementedContracts.Add(contract.ConfigurationName, contract);

            return description;
        }

        protected override void ApplyConfiguration()
        {
            if (this.TemplateServiceElement != null)
            {
                this.ApplyConfigurationFromTemplateServiceElement();
            }
            else
            {
                this.ApplyConfigurationFromScratch();
            }

            this.ListenUri = this.Description.Endpoints[0].ListenUri;
        }

        private void ApplyConfigurationFromTemplateServiceElement()
        {
            var absoluteAddressUri = this.ServiceEndpointFactory.GetAbsoluteUri(this.EndpointInfo, this.ReceivePortBaseAddresses);

            var serviceEndpointElement = new ServiceEndpointElement(absoluteAddressUri, this.ServiceEndpointFactory.GetContractType(this.EndpointInfo).FullName);

            var templateServiceEndpointElement = this.TemplateServiceElement.Endpoints.Cast<ServiceEndpointElement>().FirstOrDefault((sep) => StringComparer.InvariantCultureIgnoreCase.Compare(sep.Name, this.EndpointInfo.EndpointConfigurationName) == 0);
            if (templateServiceEndpointElement == null)
            {
                // If the serviceElement doesn't contain an endpoint with the specified name,
                //  we create one from scratch
                serviceEndpointElement.Binding = this.ServiceEndpointFactory.GetBindingConfigurationName(this.EndpointInfo);
            }
            else
            {
                // If the serviceElement contains an endpoint with the specified name,
                //  we copy that but override its Address
                serviceEndpointElement.Binding = templateServiceEndpointElement.Binding;
                serviceEndpointElement.BehaviorConfiguration = templateServiceEndpointElement.BehaviorConfiguration;
                serviceEndpointElement.BindingConfiguration = templateServiceEndpointElement.BindingConfiguration;
                serviceEndpointElement.EndpointConfiguration = templateServiceEndpointElement.EndpointConfiguration;
                serviceEndpointElement.Headers.Headers = templateServiceEndpointElement.Headers.Headers;
                
                // Can't set the Identity
                //serviceEndpointElement.Identity = templateServiceEndpointElement.Identity;
            }

            // Copy the 'template' ServiceElement
            var dynamicServiceElement = new ServiceElement(this.TemplateServiceElement.Name);
            dynamicServiceElement.BehaviorConfiguration = this.TemplateServiceElement.BehaviorConfiguration;
            dynamicServiceElement.Endpoints.Add(serviceEndpointElement);

            LoadConfigurationSection(dynamicServiceElement);
        }

        protected void ApplyConfigurationFromScratch()
        {
            var serviceEndpoint = this.ServiceEndpointFactory.CreateDynamicServiceEndpoint(this, this.EndpointInfo, this.ReceivePortBaseAddresses);
            this.Description.Endpoints.Add(serviceEndpoint);
        }
    }
}
